#include <Arduino.h>

#include "ezTime.h"

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>


const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0

// The private things go in an anonymous namespace

#define NTP_PACKET_SIZE			48
#define NTP_LOCAL_PORT			4242
#define NTP_TIMEOUT				1500			// milliseconds
#define LEAP_YEAR(Y)	( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )
#define SECS_PER_DAY	(86400UL)


time_t Timezone::nowUTC() {
	time_t t;
	//uint32_t m = millis();
	uint64_t m = micros64() / 1000;
	//t = _last_sync_time + ((m - _last_sync_millis) / 1000);
	uint32_t diff = ((m - _last_sync_micros) / 1000);
	t = _last_sync_time + diff;
	return t;
}


void Timezone::breakTime(tmElements_t &tm){
		
	time_t now = nowUTC(); 
	time_t timeInput = tzTime(now);
	// break the given time_t into time components
	// this is a more compact version of the C library localtime function
	// note that year is offset from 1970 !!!

	uint8_t year;
	uint8_t month, monthLength;
	uint32_t time;
	unsigned long days;

	time = (uint32_t)timeInput;
	tm.Second = time % 60;
	time /= 60; // now it is minutes
	tm.Minute = time % 60;
	time /= 60; // now it is hours
	tm.Hour = time % 24;
	time /= 24; // now it is days
	tm.Wday = ((time + 4) % 7) + 1;  // Sunday is day 1 

	year = 0;  
	days = 0;
	while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
		year++;
	}
	tm.Year = year; // year is offset from 1970 

	days -= LEAP_YEAR(year) ? 366 : 365;
	time  -= days; // now it is days in this year, starting at 0

	days=0;
	month=0;
	monthLength=0;
	for (month=0; month<12; month++) {
		if (month==1) { // february
			if (LEAP_YEAR(year)) {
				monthLength=29;
			} else {
				monthLength=28;
			}
		} else {
			monthLength = monthDays[month];
		}

		if (time >= monthLength) {
			time -= monthLength;
		} else {
			break;
		}
	}
	tm.Month = month + 1;  // jan is month 1  
	tm.Day = time + 1;     // day of month
}

//void Timezone::setTime1(time_t t, unsigned long measured_at){
void Timezone::setTime1(time_t t){
	//int32_t correction = ( (t - _last_sync_time) * 1000 ) - ( measured_at - _last_sync_millis );
	_last_sync_time = t;
	//_last_sync_millis = measured_at;
	_last_sync_micros = measured_at;
}

void Timezone::req(const String server, WiFiUDP* udp) {

	// Send NTP packet
	byte buffer[NTP_PACKET_SIZE];
	memset(buffer, 0, NTP_PACKET_SIZE);
	buffer[0] = 0b11100011;		// LI, Version, Mode
	buffer[1] = 0;   			// Stratum, or type of clock
	buffer[2] = 9;				// Polling Interval (9 = 2^9 secs = ~9 mins, close to our 10 min default)
	buffer[3] = 0xEC;			// Peer Clock Precision
								// 8 bytes of zero for Root Delay & Root Dispersion
	buffer[12]  = 'X';			// "kiss code", see RFC5905
	buffer[13]  = 'E';			// (codes starting with 'X' are not interpreted)
	buffer[14]  = 'Z';
	buffer[15]  = 'T';	

	udp->flush();
	udp->begin(NTP_LOCAL_PORT);
	started = micros64() / 1000;
	udp->beginPacket(server.c_str(), 123); //NTP requests are to port 123
	udp->write(buffer, NTP_PACKET_SIZE);
	udp->endPacket();
}

bool Timezone::isRes(WiFiUDP* udp) {
	return udp->parsePacket();
}

bool Timezone::res(WiFiUDP* udp, time_t &t) {
	byte buffer[NTP_PACKET_SIZE];
	memset(buffer, 0, NTP_PACKET_SIZE);

	udp->read(buffer, NTP_PACKET_SIZE);
	udp->stop();													// On AVR there's only very limited sockets, we want to free them when done.

	//print out received packet for debug
	int i;
	// debug(F("Received data:"));
	// for (i = 0; i < NTP_PACKET_SIZE; i++) {
	// 	if ((i % 4) == 0) {
	// 		debugln();
	// 		debug(String(i) + ": ");
	// 	}
	// 	debug(buffer[i], HEX);
	// 	debug(F(", "));
	// }
	// debugln();

	//prepare timestamps
	uint32_t highWord, lowWord;	
	highWord = ( buffer[16] << 8 | buffer[17] ) & 0x0000FFFF;
	lowWord = ( buffer[18] << 8 | buffer[19] ) & 0x0000FFFF;
	uint32_t reftsSec = highWord << 16 | lowWord;				// reference timestamp seconds

	highWord = ( buffer[32] << 8 | buffer[33] ) & 0x0000FFFF;
	lowWord = ( buffer[34] << 8 | buffer[35] ) & 0x0000FFFF;
	uint32_t rcvtsSec = highWord << 16 | lowWord;				// receive timestamp seconds

	highWord = ( buffer[40] << 8 | buffer[41] ) & 0x0000FFFF;
	lowWord = ( buffer[42] << 8 | buffer[43] ) & 0x0000FFFF;
	uint32_t secsSince1900 = highWord << 16 | lowWord;			// transmit timestamp seconds

	highWord = ( buffer[44] << 8 | buffer[45] ) & 0x0000FFFF;
	lowWord = ( buffer[46] << 8 | buffer[47] ) & 0x0000FFFF;
	uint32_t fraction = highWord << 16 | lowWord;				// transmit timestamp fractions	

	//check if received data makes sense
	//buffer[1] = stratum - should be 1..15 for valid reply
	//also checking that all timestamps are non-zero and receive timestamp seconds are <= transmit timestamp seconds
	if ((buffer[1] < 1) or (buffer[1] > 15) or (reftsSec == 0) or (rcvtsSec == 0) or (rcvtsSec > secsSince1900)) {
		// we got invalid packet
		//triggerError(INVALID_DATA); 
		return false;
	}

	// Set the t and measured_at variables that were passed by reference
	uint64_t done = micros64() / 1000;
	//info(F("success (round trip ")); info(done - started); infoln(F(" ms)"));
	t = secsSince1900 - 2208988800UL;					// Subtract 70 years to get seconds since 1970
	uint16_t ms = fraction / 4294967UL;					// Turn 32 bit fraction into ms by dividing by 2^32 / 1000 
	measured_at = done - ((done - started) / 2) - ms;	// Assume symmetric network latency and return when we think the whole second was.
		
	return true;
}

Timezone::Timezone() {
}

time_t Timezone::tzTime(time_t t) {

	int8_t offset_hr = 0;
	uint8_t offset_min = 0;

	bool ignore_nums = false;
	char c = 1; // Dummy value to get while(newchar) started
	uint8_t strpos = 0;

	int16_t std_offset = -8 * 60;
	
	return t - std_offset * 60LL;

}
